/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package nl.unimaas.games.tron.player.mcts;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import nl.unimaas.games.tron.engine.Board;
import nl.unimaas.games.tron.util.NGram;

/**
 *
 * @author Cliff Laschet
 */
public class NGramPlayout implements PlayoutStrategy {

    private int firstMove = -1;
    private int secondMove = -1;
    private ArrayList<Integer> history = new ArrayList<Integer>();
    private int boardSize;
    private NGram nGram;
    private double alphaRandom = 0.1;
    private Random rnd = new Random();
    private boolean syncMoves = false;

    /**
     * 
     * @param boardSize
     * @param alphaRandom
     * @param syncMoves 
     */
    public NGramPlayout(int boardSize, double alphaRandom, boolean syncMoves) {
        this.nGram = new NGram(boardSize);
        this.boardSize = boardSize;
        this.alphaRandom = alphaRandom;
        this.syncMoves = syncMoves;
    }

    /**
     * 
     * @param board
     * @param x
     * @param y
     * @param playerNr
     * @return 
     */
    @Override
    public int getMove(Board board, int x, int y, int playerNr) {
        if (this.syncMoves) {
            return this.getSyncMove(board, x, y, playerNr);
        } else {
            return this.getASyncMove(board, x, y, playerNr);
        }
    }

    /**
     * 
     * @param board
     * @param x
     * @param y
     * @param playerNr
     * @return 
     */
    private int getSyncMove(Board board, int x, int y, int playerNr) {
        if (this.alphaRandom >= 1) {
            return PlayoutStrategy.RANDOM.getMove(board, x, y, playerNr);
        }

        ArrayList<Integer> moves = board.getValidMovePositions(x, y);
        int move = -1;
        int lastElementIndex = history.size() - 1;
        if ((!moves.isEmpty()) && (rnd.nextDouble() > this.alphaRandom)) {
            if (history.size() >= 2) {
                if (history.size() >= 4) {
                    int secondIndex = lastElementIndex - 2;
                    int firstIndex = lastElementIndex - 3;
                    if (lastElementIndex % 2 == 1) {
                        secondIndex = lastElementIndex;
                        firstIndex = lastElementIndex - 3;
                    }
                    move = nGram.getAvgPosition(history.get(firstIndex), history.get(secondIndex), moves);
                } else {
                    int previousMoveIndex = lastElementIndex - 2;
                    if (lastElementIndex % 2 == 1) {
                        previousMoveIndex = lastElementIndex;
                    }
                    //move = nGram.getBiPosition(history.get(previousMoveIndex), moves);
                }
            } else {
                //move = nGram.getUniMove(moves);
            }

            if (move != -1) {
                int x2 = Board.posToX(move);
                int y2 = Board.posToY(move);
                history.add(move);
                return Board.getMoveFromPositions(x, y, x2, y2);
            } else {
                int result = PlayoutStrategy.RANDOM.getMove(board, x, y, playerNr);
                move = Board.getPositionFromMove(x, y, result);
                history.add(move);
                return result;
            }
        } else {
            int result = PlayoutStrategy.RANDOM.getMove(board, x, y, playerNr);
            move = Board.getPositionFromMove(x, y, result);
            history.add(move);
            return result;
        }
    }

    /**
     * 
     * @param board
     * @param x
     * @param y
     * @param playerNr
     * @return 
     */
    private int getASyncMove(Board board, int x, int y, int playerNr) {
        ArrayList<Integer> moves = board.getValidMovePositions(x, y);
        int move = -1;
        if ((!moves.isEmpty()) && (rnd.nextDouble() > this.alphaRandom)) {
            if (secondMove != -1) {
                if (firstMove != -1) {
                    move = nGram.getAvgPosition(firstMove, secondMove, moves);
                } else {
                    //move = nGram.getBiPosition(secondMove, moves);
                }
            } else {
                //move = nGram.getUniPosition(moves);
            }

            if (move != -1) {
                firstMove = secondMove;
                secondMove = move;
                history.add(move);
                int x2 = Board.posToX(move);
                int y2 = Board.posToY(move);
                return Board.getMoveFromPositions(x, y, x2, y2);
            } else {
                int result = PlayoutStrategy.RANDOM.getMove(board, x, y, playerNr);
                move = Board.getPositionFromMove(x, y, result);
                history.add(move);
                firstMove = secondMove;
                secondMove = move;
                return result;
            }
        } else {
            int result = PlayoutStrategy.RANDOM.getMove(board, x, y, playerNr);
            move = Board.getPositionFromMove(x, y, result);
            history.add(move);
            firstMove = secondMove;
            secondMove = move;
            return result;
        }
    }

    /**
     * 
     * @param startingPlayer
     * @param playoutPlayer
     * @param reward
     * @param syncMoves 
     */
    public void onFinishPlayout(int startingPlayer, int playoutPlayer, double reward, boolean syncMoves) {
        //No need to update the ngram when we only perform random moves in the playout.
        if (this.alphaRandom >= 1.0) {
            return;
        }

        Iterator iterator = history.iterator();
        int[] moveList = new int[history.size()];
        int i = 0;
        while (iterator.hasNext()) {
            moveList[i] = (Integer) iterator.next();
            i++;
        }
        this.nGram.update(moveList, startingPlayer, playoutPlayer, reward, syncMoves);
        this.history.clear();
        firstMove = -1;
        secondMove = -1;
    }

    /**
     * 
     */
    public void resetHistory() {
        this.history.clear();
    }

    /**
     * 
     */
    public void resetNGram() {
        this.resetHistory();
        this.firstMove = -1;
        this.secondMove = -1;
        this.nGram.reset();
    }

    /**
     * 
     * @return 
     */
    public NGram getNGram() {
        return this.nGram;
    }

    /**
     * 
     * @return 
     */
    @Override
    public String toString() {
        return "N-Gram";
    }

    /**
     * 
     * @param firstMove
     * @param secondMove 
     */
    public void setPreviousMoves(int firstMove, int secondMove) {
        if (this.syncMoves) {
            history.clear();
            history.add(firstMove);
            history.add(secondMove);
        } else {
            this.firstMove = firstMove;
            this.secondMove = secondMove;
        }
    }
}
